﻿using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Yoozoo.Gameplay.RTS
{
    public class UEInstanceAnimation
    {
        public const int MeshInstanceMaxNum = 1023;
        public enum ModeLevel
        {
            Low = 0,
            Medium,
        }

        private List<GPUSkinning_SpawnObject> spawnObjects = new List<GPUSkinning_SpawnObject>();
        
        private Dictionary<GPUSkinning_SpawnObject,int> _activeObjDic = new Dictionary<GPUSkinning_SpawnObject, int>();
        //存储动画数据
        public GPUSkinning_AllBoneAnimation allBoneAnimation = null;

        public GPUSkinning_AllAnimation allAnimation;
        public int totalFrameCount;
        
        private Material newMtrl = null;
        private Material shadowMtrl = null;
        public int totalSpawnObjectsCount = 0;
        //材质Block
        private List<MaterialPropertyBlock> mtrPBs = new List<MaterialPropertyBlock>();
        
        public Dictionary<int, int> m_TransitionHashCodeIndexMap = new Dictionary<int, int>();
        
        public Dictionary<int, int> m_StateNameHashCodeIndexMap = new Dictionary<int, int>();

        public Dictionary<GPUSkinningAnimatorState, Dictionary<int, List<GPUSkinningAnimatorTransition>>>
            m_ParamToTransition;
        private Mesh m_mediumMesh;
        private Mesh m_lowMesh;
        private Mesh currentMesh;
        public Texture2D tex2D;

        public bool enableDissolve = false;
        public bool enableShadowColor = false;
        public bool enableShadowHeight = false;
        
        private Color m_campColor;
        
        public Material[] materials = new Material[2];

        private Matrix4x4[] matrices = new Matrix4x4[0];
        private float[] frameIndexes =      new float[0];
        private float[] frameIndexFusion =  new float[0];
        private float[] fusionProgress =    new float[0];
        private Vector4[] rimColors = new Vector4[0];
        private float[] rimIntensity = new float[0];
        private float[] fresnelExp = new float[0];
        private float[] alpha = new float[0];
        private Vector4[] maskColor = new Vector4[0];
        private float[] inRoom = new float[0];
        private float[] breathingLight = new float[0];
        private float[] dissolveAmount = new float[0];
        private Vector4[] shadowColor = new Vector4[0];
        private float[] shadowHeight = new float[0];
        private MaterialPropertyBlock properties  = new MaterialPropertyBlock();
        //Shader属性引用
        int _MaskColor = Shader.PropertyToID("_MaskColor");
        int _MainColor = Shader.PropertyToID("_Color");
        int _Opacity = Shader.PropertyToID("_Opacity");
        int _FrameIndex = Shader.PropertyToID("_FrameIndex");
        int _FrameIndexFusion = Shader.PropertyToID("_FrameIndexFusion");
        int _FusionProgress = Shader.PropertyToID("_FusionProgress");
        int _FresnelExp = Shader.PropertyToID("_FresnelExp");
        int _Alpha = Shader.PropertyToID("_GPUSkinAlpha");
        int _RimIntensity = Shader.PropertyToID("_RimIntensity");
        int _RimColor = Shader.PropertyToID("_RimColor");
        int _InRoom = Shader.PropertyToID("_InRoom");
        int _BreathingLight = Shader.PropertyToID("_breathingLight");
        int _DissolveAmount = Shader.PropertyToID("_DissolveAmount");
        int _ShadowColor = Shader.PropertyToID("_ShadowColor");
        int _ShadowHeight = Shader.PropertyToID("_ShadowHeight");
        int m_layer;

        ModeLevel m_modelLevel = ModeLevel.Medium;
        private int beforeTotalSpawnObjectCount = 0;
        private int rendererCount = 0;
        private int animatorCount = 0;
        public int RendererCount => rendererCount;
        public int AnimatorCount => animatorCount;

        private Dictionary<string,int> paramNameToHash;
        private int[] paramIndexToHashCode;

        private bool hasMesh = false;
        public string Name = "";

        private string singleTexturePath;

        public void DestroyTexture()
        {
            if (!string.IsNullOrEmpty(singleTexturePath))
            {
                GPUInstancingManager.RemoveInstancingTexture(singleTexturePath);
                singleTexturePath = null;
            }
        }
        
        public void Dispose()
        {
            this.tex2D = null;
        }
        public void SetModeLevel(ModeLevel level)
        {
            m_modelLevel = level;
            currentMesh = m_modelLevel==ModeLevel.Low?m_lowMesh:m_mediumMesh;
        }
        public void SetLayer(int layer)
        {
            m_layer = layer;
        }

        public void AddData(int configId, GPUSkinning_AllBoneAnimation data, Material mat, Mesh mediumMesh,
            Mesh lowMesh, Material shadowMat, Color campColor,GPUSkinning_AllAnimation animationInfo = null)
        {
            allBoneAnimation = data;
            Name = "GpuInstancing: " + data.name;
            m_campColor = campColor;

            newMtrl = Object.Instantiate(mat);
            var newShadowMat = Object.Instantiate(shadowMat);
            shadowMtrl = newShadowMat;

            currentMesh = m_mediumMesh = mediumMesh;
            if (currentMesh == null)
            {
                Debug.LogError("Mesh为空 " + mat.name);
                hasMesh = false;
            }
            else
            {
                hasMesh = true;
            }

            m_lowMesh = lowMesh;

            singleTexturePath = null;
            Texture2D animationTexture = null;
            if (animationInfo == null)
            {
                animationTexture = GPUInstancingManager.GetInstance().AnimationTex;
                allAnimation = GPUInstancingManager.GetInstance().AllAnimation;
            }
            else
            {
                singleTexturePath = animationInfo.textureDataPath;
                animationTexture = GPUInstancingManager.GetInstancingTexture(animationInfo);
                allAnimation = animationInfo;
            }
            totalFrameCount = allAnimation == null ? 1 : allAnimation.totalAnimationLength;
            tex2D = animationTexture;
            newMtrl.SetTexture("_AnimationTex", animationTexture);
            newMtrl.SetVector("_AnimationTexSize", new Vector4(animationTexture.width, animationTexture.height, 0, 0));
            newShadowMat.SetTexture("_AnimationTex", animationTexture);
            newShadowMat.SetVector("_AnimationTexSize", new Vector4(animationTexture.width, animationTexture.height, 0, 0));

#if UNITY_EDITOR && SKIN_DEBUG
            m_TransitionHashCodeIndexMap = new Dictionary<int, int>();
            for (int i = 0; i < allBoneAnimation.m_Transitions.Length; i++)
            {
                m_TransitionHashCodeIndexMap[(allBoneAnimation.m_Transitions[i].m_Source + "=>" + allBoneAnimation.m_Transitions[i].m_Target).GetHashCode()]
 = i;
            }
#endif
            int stateCount = allBoneAnimation.m_AllStates.Length;
            int parameterCount = allBoneAnimation.parameters.Length;

            // 参数列表中的序号-->对应参数hashcode
            paramIndexToHashCode = new int[parameterCount];
            // 参数名字-->对应参数HashCode
            paramNameToHash = new Dictionary<string, int>(parameterCount);
            for (int j = 0; j < parameterCount; j++)
            {
                paramIndexToHashCode[j] = allBoneAnimation.parameters[j].hashCode;
                paramNameToHash.Add(allBoneAnimation.parameters[j].name, paramIndexToHashCode[j]);
            }

            // 状态Name的HashCode--->状态列表中索引
            m_StateNameHashCodeIndexMap = new Dictionary<int, int>(stateCount);
            // 参数索引--> 查找对应的Transition用(切换Trigger,找到需要切换到哪个Transition)
            m_ParamToTransition =
                new Dictionary<GPUSkinningAnimatorState, Dictionary<int, List<GPUSkinningAnimatorTransition>>>(
                    stateCount);

            for (int stateIndex = 0; stateIndex < stateCount; stateIndex++)
            {
                var state = allBoneAnimation.m_AllStates[stateIndex];
                var stateHashCode = state.m_Name.GetHashCode();
                m_StateNameHashCodeIndexMap[stateHashCode] = stateIndex;
                int tempicCount = 0;
                //计算一下字典长度，避免后面反复resize
                foreach (var transition in state.transitions)
                {
                    if (transition.HasCondition)
                    {
                        tempicCount += transition.m_ConditionHashes.Length;
                    }
                }
                var tempDic = new Dictionary<int, List<GPUSkinningAnimatorTransition>>(tempicCount);
                
                foreach (var transition in state.transitions)
                {
                    if (transition.HasCondition)
                    {
                        foreach (var chash in transition.m_ConditionHashes)
                        {
                            List<GPUSkinningAnimatorTransition> transitionList;
                            if (!tempDic.TryGetValue(chash, out transitionList))
                            {
                                transitionList = new List<GPUSkinningAnimatorTransition>(transition.m_ConditionHashes.Length);
                                if (chash < parameterCount)
                                {
                                    var paramx = allBoneAnimation.parameters[chash];
                                    if (tempDic.ContainsKey(paramx.hashCode))
                                    {
//                                        UnityEngine.Debug.LogError("建立索引出错-->已经包含了key...");
                                        //todo感觉这个没啥问题
                                    }
                                    else
                                    {
                                        tempDic.Add(paramx.hashCode, transitionList);
                                    }

                                }
                                else
                                {
                                    UnityEngine.Debug.LogErrorFormat("未找到parameters Index = {0}  state = {1}", chash,
                                        state.m_Name);
                                }

                            }

                            transitionList.Add(transition);
                        }
                    }
                }

                m_ParamToTransition.Add(state, tempDic);
            }

            materials[0] = newMtrl;
            materials[1] = newShadowMat;

            //判断是否支持
            foreach (var matx in materials)
            {
                if (!matx.shader.isSupported)
                {
                    Debug.LogErrorFormat("Shader {0} isSupported = {1}", matx.shader.name, matx.shader.isSupported);
                }
            }
        }

        public void SetMainColor(Color color)
        {
            if (newMtrl != null)
            {
                newMtrl.SetColor(_MainColor, color);
            }
        }

        public GPUSkinning_SpawnObject AddSpawnObject(Transform trans)
        {

            GPUSkinning_SpawnObject spawnObject = new GPUSkinning_SpawnObject();
            spawnObject.useFrameLerp = GPUInstancingManager.isSurvivor;
            spawnObjects.Add(spawnObject);

            var model = trans.GetComponent<ModelInstaning>();
            spawnObject.model = model;

            //var components = trans.GetComponents<Component>();
            //foreach (var component in components)
            //{
            //    if (component is GPUSkinning_IAnimationEventReceiver eventReceiver)
            //    {
            //        spawnObject.eventReceiver = eventReceiver;
            //        break;
            //    }
            //}
            spawnObject.eventReceiver = trans.GetComponent<GPUSkinning_IAnimationEventReceiver>();

            m_layer = trans.gameObject.layer;

            spawnObject.allBoneAnimation = this.allBoneAnimation;
            spawnObject.allAnimation = allAnimation;
            spawnObject.totalFrameCount = totalFrameCount;
            spawnObject.m_StateNameHashCodeIndexMap = m_StateNameHashCodeIndexMap;
            spawnObject.m_TransitionHashCodeIndexMap = m_TransitionHashCodeIndexMap;
            spawnObject.m_ParamToTransition = m_ParamToTransition;

            spawnObject.paramIndexToHashCode = paramIndexToHashCode;
            spawnObject.paramNameToHash = paramNameToHash;
            totalSpawnObjectsCount = spawnObjects.Count;
            spawnObject.Initial(trans, model);
            spawnObject.InjectUEInstanceAnimation(this);
            return spawnObject;
        }
    
        public void RemoveSpawnObject(GPUSkinning_SpawnObject spawnObj)
        {
            int idx = spawnObjects.IndexOf(spawnObj);
            if (idx < 0) return;

            spawnObjects.RemoveAt(idx);
            totalSpawnObjectsCount = spawnObjects.Count;
        }

        public void SetRendererEnabled(bool active)
        {
            rendererCount = active ? rendererCount + 1 : rendererCount - 1;
        }

        public void SetAnimatorEnabled(bool active)
        {
            animatorCount = active ? animatorCount + 1 : animatorCount - 1;
        }

        public void Update()
        {
            if (!SystemInfo.supportsInstancing)
            {
                return;
            }
            if (rendererCount <= 0 && animatorCount <= 0)
            {
                return;
            }

            float smoothDeltaTime = Time.smoothDeltaTime;

            //变多了就重新分配
            if (rendererCount > matrices.Length)
            {
                if (properties != null)
                {
                    properties.Clear();

                }

                properties = new MaterialPropertyBlock();
                float growthfactor = matrices.Length < 50 ? 1.5f : 1.3f;

                int count = Math.Max(rendererCount, Mathf.FloorToInt(matrices.Length * growthfactor));
                matrices = new Matrix4x4[count];
                frameIndexes = new float[count];
                frameIndexFusion = new float[count];
                fusionProgress = new float[count];

                rimColors = new Vector4[count];
                rimIntensity = new float[count];
                fresnelExp = new float[count];
                alpha = new float[count];
                maskColor = new Vector4[count];
                inRoom = new float[count];
                breathingLight = new float[count];
                if (enableDissolve)
                {
                    dissolveAmount = new float[count];
                }
                if (enableShadowColor)
                {
                    shadowColor = new Vector4[count];
                }

                if (enableShadowHeight)
                {
                    shadowHeight = new float[count];
                }
            }

            //int displaySubMesh1 = 0;
            int i = 0;
            GPUSkinning_SpawnObject spawn;
            for (int spawnIndex = 0; spawnIndex < totalSpawnObjectsCount; spawnIndex++)
            {
                spawn = this.spawnObjects[spawnIndex];
                if (spawn.AnimatorEnabled)
                {
                    spawn.Update(smoothDeltaTime);
                }

                if (spawn.RendererEnabled && tex2D)
                {
                    matrices[i] = spawn.cacheLocalToWorldMatrix;
                    frameIndexes[i] = PackFrameIndex(spawn.frameIndex);
                    frameIndexFusion[i] = PackFrameIndex(spawn.m_FrameIndexFusion,spawn.frameFusionOffset);
                    fusionProgress[i] = spawn.m_Progress;

                    rimColors[i] = spawn.RimColor;
                    rimIntensity[i] = spawn.RimIntensity;
                    fresnelExp[i] = spawn.FresnelExp;
                    alpha[i] = spawn.Alpha;
                    maskColor[i] = spawn.MaskColor;
                    inRoom[i] = spawn.InRoom;
                    breathingLight[i] = spawn.BreathLight;
                    if (enableDissolve)
                    {
                        dissolveAmount[i] = spawn.DissolveAmount;
                    }
                    if (enableShadowColor)
                    {
                        shadowColor[i] = spawn.ShadowColor;
                    }

                    if (enableShadowHeight)
                    {
                        shadowHeight[i] = spawn.ShadowHeight;
                    }
                    i++;
                }
            }

            if (rendererCount >= MeshInstanceMaxNum)
                DrawInstancing(MeshInstanceMaxNum);
            else if (rendererCount > 0)
                DrawInstancing(rendererCount);
        }

        private void DrawInstancing(int drawNum)
        {
            if (!currentMesh) return;   // Bugly: ArgumentNullException Value cannot be null. Parameter name: mesh
            if (drawNum <= 0 || !hasMesh) return;
            properties.SetFloatArray(_FrameIndex, frameIndexes);
            properties.SetFloatArray(_FrameIndexFusion, frameIndexFusion);
            properties.SetFloatArray(_FusionProgress, fusionProgress);
            
            properties.SetVector(_RimColor, new Vector4(241/255f,45/255f,31/255f,1));
            properties.SetFloatArray(_RimIntensity, rimIntensity);
            properties.SetFloatArray(_FresnelExp, fresnelExp);
            properties.SetFloatArray(_Alpha, alpha);
            properties.SetVectorArray(_MaskColor, maskColor);
            properties.SetFloatArray(_InRoom, inRoom);
            properties.SetFloatArray(_BreathingLight, breathingLight);
            if (enableDissolve)
            {
                properties.SetFloatArray(_DissolveAmount, dissolveAmount);
            }
            if (enableShadowColor)
            {
                properties.SetVectorArray(_ShadowColor,shadowColor);
            }

            if (enableShadowHeight)
            {
                properties.SetFloatArray(_ShadowHeight, shadowHeight);
            }
          
            Graphics.DrawMeshInstanced(currentMesh, 0, materials[0], matrices, drawNum, properties, UnityEngine.Rendering.ShadowCastingMode.Off, false, m_layer);

            if (GPUInstancingManager.IsShadowActive) //影子
                Graphics.DrawMeshInstanced(currentMesh, currentMesh.subMeshCount > 1 ? 1:0, materials[1], matrices, drawNum, properties, UnityEngine.Rendering.ShadowCastingMode.Off, false, m_layer);

            
        }
        public void MoveChildToLast(GPUSkinning_SpawnObject spawnObj)
        {
            int idx = spawnObjects.IndexOf(spawnObj);
            if (idx < 0) return;

            spawnObjects.RemoveAt(idx);
            spawnObjects.Insert(spawnObjects.Count, spawnObj);

        }

#if UNITY_EDITOR
        public void DrawSpawnObjects()
        {
            for (var i = 0; i < spawnObjects.Count; i++)
            {
                var spawnObject = spawnObjects[i];

                Handles.Label(spawnObject.model.m_transform.position + Vector3.up * 2, 
                    $"{spawnObject.model.name}\n" +
                    $"Renderer: {spawnObject.RendererEnabled}, Animator: {spawnObject.AnimatorEnabled}\n" +
                    $"[{i+1}]: {spawnObject.m_CurrentState.m_Name}");
            }
        }
#endif
        public float PackFrameIndex(float frameIndex,int frameOffset = 0)
        {
            if (allAnimation == null)
                return frameIndex;
            
            int frame = (int)(frameIndex * totalFrameCount);
            frame += frameOffset;
            int x = frame % allAnimation.textureWidth;
            int y = frame / allAnimation.textureWidth * allAnimation.boneCount * 2;

            float nx = (x + 0.5f) / allAnimation.textureWidth;
            //float ny = (y + 0.5f) / AllAnimation.textureHeight;

            float vx = Mathf.Clamp(nx, 0.0f, 0.9999f);
            //float vy = Mathf.Clamp01(ny);
            float pack = vx + y;

            return pack;
        }
        
        public Material GetMainMaterial()
        {
            return newMtrl;
        }

        public Material GetShadowMaterial()
        {
            return shadowMtrl;
        }
    }
}
